﻿using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing.Design;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Project;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.WowAddonStudio.Project.Import;
using Microsoft.WowAddonStudio.Project.OptionPages;
using Microsoft.WowAddonStudio.Project.Services;
using Microsoft.WowAddonStudio.Services;
using Microsoft.WowAddonStudio.TocEditor;

namespace Microsoft.WowAddonStudio.Project
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // A Visual Studio component can be registered under different regitry roots; for instance
    // when you debug your package you want to register it in the experimental hive. This
    // attribute specifies the registry root to use if no one is provided to regpkg.exe with
    // the /root switch.
    [DefaultRegistryRoot(@"Software\Microsoft\AppEnv\9.0\Apps\WowAddonStudio")]
    // Register the package and use only managed resources
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // Provides a project factory so that addon projects can be created.
    [ProvideProjectFactory(typeof (WowProjectFactory), "World of Warcraft",
        "World of Warcraft Addon Project Files (*.wowproj);*.wowproj", "wowproj", "wowproj", @".\NullPath",
        LanguageVsTemplate = "WowAddon")]
    // Provides project items
    [ProvideProjectItem(typeof (WowProjectFactory), "World of Warcraft Items", @".\NullPath", 500)]

    // Provides the property pages
    [ProvideObject(typeof (TocEditorAddOnInfoPage))]
    [ProvideObject(typeof (TocEditorFilesPage))]

    // Provides the option page
    [ProvideOptionPage(typeof(AddOnStudioGeneralOptionsDialogPage), "AddOn Studio", "General", 120, 121, true)]
    [ProvideOptionPage(typeof(FrameXmlDesignerGeneralOptionsDialogPage), "FrameXMLDesigner", "Miscellaneous", 110, 111, true)]
    [ProvideOptionPage(typeof(WowBenchGeneralOptionsDialogPage), "WoWBench", "General", 103, 104, true)]

    // Provides the Settings service
    [ProvideService(typeof (IAddonStudioSettings), ServiceName = "AddOn Studio for World of Warcraft Project Settings")]
    // Provides the WoW locator service
    [ProvideService(typeof (IWowLocatorService), ServiceName = "World of Warcraft Locator Service")]

    // In order be loaded inside Visual Studio in a machine that has not the VS SDK installed, 
    // package needs to have a valid load key (it can be requested at 
    // http://msdn.microsoft.com/en-us/vsx/cc655795.aspx). This attributes tells the shell that this 
    // package has a load key embedded in its resources.
    [ProvideLoadKey("Standard", "1.0", "World of Warcraft Project Package", "WowAddonStudio", 102)]

    // Provides the MSBuild targets that are used by the projects
    [ProvideMSBuildTargets("WowAddonStudio_2.0", @"%ProgramFiles%\MSBuild\Microsoft\WowAddonStudio\2.0\WowAddonStudio.targets")]
    // Provides toolbox items
    [ProvideToolboxItems(3, true)]
    // Provide menu items
    [ProvideMenuResource(1000, 1)]
    // Provide product information in the Help/About dialog
    [InstalledProductRegistration(false, "#100", "#101", "2.0", LanguageIndependentName = "AddOn Studio for World of Warcraft", IconResourceID = 400)]
    // The Guid of the WowProjectPackage
    [Guid(GuidStrings.WowProjectPackage)]
    public sealed class WowProjectPackage : ProjectPackage
    {
        private const string tutorialsRelativePath = @"Tutorials\AddOn Studio for World of Warcraft Tutorial.html";
        private const string wowAddonStudioFilterName = "WowAddonStudio";
        private const string toolboxTabName = "World of Warcraft";
        private const string frameXmlComponentsFilename = "FrameXml.Components.dll";

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initilaization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            try
            {
                base.Initialize();

                // Remove SolutionListenerForProjectUpdates from collection of SolutionListeners as we do not support references
                SolutionListenerForProjectReferenceUpdate referenceUpdateListener = SolutionListeners.OfType<SolutionListenerForProjectReferenceUpdate>().SingleOrDefault();
                if (referenceUpdateListener != null)
                {
                    referenceUpdateListener.Dispose();
                    SolutionListeners.Remove(referenceUpdateListener);
                }

                // Hook designer event services
                var eventService = (IDesignerEventService) GetService(typeof (IDesignerEventService));
                eventService.DesignerCreated += OnDesignerCreated;

                // Hook ToolboxInitialized event
                ToolboxInitialized += OnToolboxInitialized;
                ToolboxUpgraded += OnToolboxUpgraded;

                // Add menu handlers
                AddMenuHandlers();

                // Add services
                AddService(typeof (IAddonStudioSettings), new AddonStudioSettings(this));
                AddService(typeof (IWowLocatorService), new WowLocatorService(this));

                // Register the WoW project factory
                RegisterProjectFactory(new WowProjectFactory(this));
            }
            catch (Exception e)
            {
                Trace.WriteLine(e);
                throw;
            }
        }

        /// <summary>
        /// Gets a service.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <returns>An instance of the <typeparamref name="TService"/> class.</returns>
        public TService GetService<TService>()
        {
            return (TService)GetService(typeof (TService));
        }

        /// <summary>
        /// Gets the dialog page.
        /// </summary>
        /// <typeparam name="TDialogPage">The type fo the dialog page.</typeparam>
        /// <returns>An instance of the <typeparamref name="TDialogPage"/> class.</returns>
        public TDialogPage GetDialogPage<TDialogPage>() where TDialogPage : class
        {
            return GetDialogPage(typeof (TDialogPage)) as TDialogPage;
        }

        /// <summary>
        /// Adds the specified service to the service container.
        /// </summary>
        /// <param name="serviceType">The type of service to add.</param>
        /// <param name="serviceInstance">An instance of the service type to add. This object must implement or inherit from the type indicated by the <paramref name="serviceType"/> parameter.</param>
        private void AddService(Type serviceType, object serviceInstance)
        {
            ((IServiceContainer) this).AddService(serviceType, serviceInstance, true);
        }

        #region Attach Menu and Command Event Handlers


        /// <summary>
        /// Adds the menu handlers.
        /// </summary>
        private void AddMenuHandlers()
        {
            // Add our command handlers for menu (commands must exist in the .vsct file)
            var menuCommandService = GetService(typeof (IMenuCommandService)) as OleMenuCommandService;

            if (menuCommandService != null)
            {
                // Add menu handlers for the menu items
                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.TutorialCreateAddon,
                               delegate { NavigateTutorialAnchor("CreatingAddon"); });
                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.TutorialDeployAddon,
                               delegate { NavigateTutorialAnchor("DeployingAddon"); });
                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.TutorialTestAddon,
                               delegate { NavigateTutorialAnchor("TestingAddon"); });
                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.TutorialCreateAce2Addon,
                               delegate { NavigateTutorialAnchor("CreatingAce2Addon"); });

                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.HelpWowAceWiki,
                               delegate { Navigate("http://www.wowace.com"); });
                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.HelpWowUiWiki,
                               delegate { Navigate("http://www.wowwiki.com"); });

                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.FileImport, OnFileImport);
                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.WoWBenchToolWindow, OnShowWoWBenchToolWindow);

                AddMenuHandlerWithQueryStatus(menuCommandService, Guids.WowProjectCmdSet, Commands.RunCode, OnRunCode,
                                              OnRunCodeBeforeQueryStatus);

                AddMenuHandler(menuCommandService, Guids.WowProjectCmdSet, Commands.ViewLayoutControl, OnLayoutControl);
            }
        }

        /// <summary>
        /// Adds the menu handler.
        /// </summary>
        /// <param name="menuCommandService">The menu command service.</param>
        /// <param name="menuGroup">The menu group.</param>
        /// <param name="command">The command.</param>
        /// <param name="handler">The handler.</param>
        /// <returns></returns>
        private static MenuCommand AddMenuHandler(IMenuCommandService menuCommandService, Guid menuGroup, int command,
                                                  EventHandler handler)
        {
            var commandId = new CommandID(menuGroup, command);
            var menuItem = new MenuCommand(handler, commandId);
            menuCommandService.AddCommand(menuItem);
            return menuItem;
        }

        /// <summary>
        /// Adds the menu handler with query status.
        /// </summary>
        /// <param name="menuCommandService">The menu command service.</param>
        /// <param name="menuGroup">The menu group.</param>
        /// <param name="command">The command.</param>
        /// <param name="handler">The handler.</param>
        /// <param name="queryStatusHandler">The query status handler.</param>
        private static void AddMenuHandlerWithQueryStatus(IMenuCommandService menuCommandService, Guid menuGroup, int command,
                                                          EventHandler handler, EventHandler queryStatusHandler)
        {
            try
            {
                // Create the command for the query status menu item.
                var queryStatusCommandID = new CommandID(menuGroup, command);
                var queryStatusMenuCommand = new OleMenuCommand(handler, queryStatusCommandID);
                queryStatusMenuCommand.BeforeQueryStatus += queryStatusHandler;
                menuCommandService.AddCommand(queryStatusMenuCommand);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex);
            }
        }

        /// <summary>
        /// Displays 'Layout Control ToolWindow' in environment.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnLayoutControl(object sender, EventArgs e)
        {
            var designerHostService = (ILayoutControllerService)GetService(typeof(ILayoutControllerService));
            if (designerHostService != null)
                designerHostService.ShowLayoutControl();
            else
                Trace.WriteLine("ILayoutControllerService not available!");
        }

        #endregion

        #region RunCode MenuItem Handlers

        /// <summary>
        /// Called when a client requests the status of the 'Run Code' command.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnRunCodeBeforeQueryStatus(object sender, EventArgs e)
        {
            var menuCommand = sender as OleMenuCommand;
            if (menuCommand != null)
            {
                menuCommand.Enabled = CanEvaluateExpression();
            }
        }

        /// <summary>
        /// Returns a flag indicating if the given lua expression can be evaluated
        /// in the context of the current Lua runtime.
        /// </summary>
        /// <returns>If the method succeeds, it returns true. If it fails, it returns false.</returns>
        private bool CanEvaluateExpression()
        {
            return !string.IsNullOrEmpty(GetSelectedExpression());
        }

        /// <summary>
        /// Reteurns the selected text in Code Window.
        /// </summary>
        /// <returns></returns>
        private string GetSelectedExpression()
        {
            string result = null;
            var dte = (DTE2) GetService(typeof (DTE));
            if (dte != null)
            {
                ProjectItem projectItem = dte.ActiveDocument.ProjectItem;
                if (projectItem != null && projectItem.Object != null)
                {
                    string metadata = ((FileNode) projectItem.Object).ItemNode.GetMetadata(ProjectFileConstants.SubType);
                    if (WowSubType.Code == metadata)
                    {
                        var document = (TextDocument) projectItem.Document.Object("TextDocument");
                        result = document.Selection.Text;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Called when a client click on the 'Rename' command.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnRunCode(object sender, EventArgs e)
        {
            var runtimeService = (ILuaRuntimeService) GetService(typeof (ILuaRuntimeService));
            runtimeService.EvaluateExpression(GetSelectedExpression());
        }

        #endregion

        #region ToolWindow Request Handlers

        /// <summary>
        /// Called when [show wo W bench tool window].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnShowWoWBenchToolWindow(object sender, EventArgs e)
        {
            var luaRuntimeService = (ILuaRuntimeService) GetService(typeof (ILuaRuntimeService));
            if (luaRuntimeService != null)
                luaRuntimeService.ShowWowBenchToolWindow();
        }

        #endregion

        #region Main MenuItem Handlers

        /// <summary>
        /// Called when [file import].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnFileImport(object sender, EventArgs e)
        {
            using (var importWizardForm = new ImportWizardForm())
            {
                if (importWizardForm.ShowDialog() == DialogResult.OK)
                {
                    var solution = (IVsSolution) GetService(typeof (SVsSolution));
                    if (solution != null)
                    {
                        Guid g = Guid.Empty;
                        IntPtr projPtr;

                        solution.CreateProject(ref g,
                                               importWizardForm.ProjectFilePath,
                                               null,
                                               null,
                                               (uint) __VSCREATEPROJFLAGS.CPF_OPENFILE,
                                               ref g,
                                               out projPtr);
                    }
                }
            }
        }

        #endregion

        #region Tutorial and Help MenuItem Handlers

        /// <summary>
        /// Navigates the specified URL.
        /// </summary>
        /// <param name="url">The URL.</param>
        private void Navigate(string url)
        {
            var webBrowsingService = GetService(typeof (SVsWebBrowsingService)) as IVsWebBrowsingService;

            if (webBrowsingService != null)
            {
                IVsWindowFrame frame;
                ErrorHandler.ThrowOnFailure(webBrowsingService.Navigate(url, 0, out frame));
            }
        }

        /// <summary>
        /// Navigates the tutorial anchor.
        /// </summary>
        /// <param name="anchorName">Name of the anchor.</param>
        private void NavigateTutorialAnchor(string anchorName)
        {
            var shell = (IVsShell) GetService(typeof (SVsShell));

            if (shell != null)
            {
                object value;
                shell.GetProperty((int) __VSSPROPID.VSSPROPID_InstallDirectory, out value);

                var installDir = (string) value;
                string tutorialsPath = Path.Combine(installDir, tutorialsRelativePath);

                if (File.Exists(tutorialsPath))
                {
                    if (!String.IsNullOrEmpty(anchorName))
                        Navigate(tutorialsPath + "#" + anchorName);
                    else
                        Navigate(tutorialsPath);
                }
                else
                    MessageBox.Show(
                        "The tutorials are missing from your installation. Please reinstall AddOn Studio for World of Warcraft.",
                        "AddOn Studio for World of Warcraft", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        #endregion


        /// <summary>
        /// Called when [designer created].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.Design.DesignerEventArgs"/> instance containing the event data.</param>
        private static void OnDesignerCreated(object sender, DesignerEventArgs e)
        {
            e.Designer.LoadComplete += OnDesignerLoadComplete;
        }

        /// <summary>
        /// Called when [designer load complete].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private static void OnDesignerLoadComplete(object sender, EventArgs e)
        {
            var host = (IDesignerHost)sender;
            TypeDescriptor.AddAttributes(host.GetDesigner(host.RootComponent), new Attribute[] { new ToolboxItemFilterAttribute(wowAddonStudioFilterName, ToolboxItemFilterType.Require) });

        }

        /// <summary>
        /// Called when [toolbox initialized].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnToolboxInitialized(object sender, EventArgs e)
        {
            var toolboxService = (IToolboxService)GetService(typeof(IToolboxService));

            string frameXmlComponentsPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), frameXmlComponentsFilename);
            var assemblyName = AssemblyName.GetAssemblyName(frameXmlComponentsPath);

            foreach (ToolboxItem item in ToolboxService.GetToolboxItems(assemblyName))
            {
                if (item.DisplayName.EndsWith("Control"))
                    item.DisplayName = item.DisplayName.Substring(0, item.DisplayName.Length - "Control".Length);
                if (item.DisplayName.EndsWith("Component"))
                    item.DisplayName = item.DisplayName.Substring(0, item.DisplayName.Length - "Component".Length);

                toolboxService.AddToolboxItem(item, toolboxTabName);
            }

            toolboxService.Refresh();
        }

        /// <summary>
        /// Called when [toolbox upgraded].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnToolboxUpgraded(object sender, EventArgs e)
        {
            var toolbox = GetService(typeof(SVsToolbox)) as IVsToolbox;
            if (toolbox == null)
                return;

            toolbox.RemoveTab(toolboxTabName);

            OnToolboxInitialized(sender, e);
        }
    }
}